﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;

namespace Babylon.Animations
{
    public abstract class AnimationUsingKeyFrameBase<T> : IAnimation
    {
        private readonly List<KeyFrame<T>> _keyFrames = new List<KeyFrame<T>>();

        private readonly ReadOnlyCollection<KeyFrame<T>> _readOnlyKeyFrames;
        public ReadOnlyCollection<KeyFrame<T>> KeyFrames { get { return _readOnlyKeyFrames; } }


        private float _startTime;
        private float _pausedDuration;
        private float _currentTime;

        public float StartTime { get { return _startTime; } }
        public float PausedDuration { get { return _pausedDuration; } }
        public float CurrentTime { get { return _currentTime; } }


        protected AnimationUsingKeyFrameBase()
        {
            _readOnlyKeyFrames = new ReadOnlyCollection<KeyFrame<T>>(_keyFrames);
        }

        public void AddKeyFrame(KeyFrame<T> keyFrame)
        {
            if (AnimationState != AnimationState.Stopped)
            {
                throw new InvalidOperationException("KeyFrames can only be modified when animation is stopped");
            }
            var following = _keyFrames.Where(f => f.KeyTime > keyFrame.KeyTime).FirstOrDefault();
            if (following != null)
                _keyFrames.Insert(_keyFrames.IndexOf(following), keyFrame);
            else
                _keyFrames.Add(keyFrame);
        }
        public void RemoveKeyFrame(KeyFrame<T> keyFrame)
        {

            if (AnimationState != AnimationState.Stopped)
            {
                throw new InvalidOperationException("KeyFrames can only be modified when animation is stopped");
            }

            _keyFrames.Remove(keyFrame);
        }

        public T BaseValue { get; set; }
        private T _currentValue;
        private bool _currentValueDirty = true;

        protected abstract T Interpolate(T first, T second, float amount);

        public T CurrentValue
        {
            get
            {
                if (!_currentValueDirty)
                    return _currentValue;

                T previous = default(T);
                float previousNormalizedKT=0;
                T next = default(T);
                float nextNormalizedKT=1;
                var globalProgress = GlobalEasingFunction(NormalizedAnimationTime);


                EasingFunction localEasingFunction = null;
                if(globalProgress>1)
                {
                    if(_keyFrames.Count ==1)
                    {
                        previous = BaseValue;
                        previousNormalizedKT = 0;
                    }
                    else
                    {
                        previous = _keyFrames[_keyFrames.Count - 2].Value;
                        previousNormalizedKT = _keyFrames[_keyFrames.Count - 2].NormalizedKeyTime;
                    }
                    next = _keyFrames[_keyFrames.Count - 1].Value;
                    nextNormalizedKT = _keyFrames[_keyFrames.Count - 1].NormalizedKeyTime;
                    localEasingFunction = _keyFrames[_keyFrames.Count - 1].EasingFunction;
                }
                else if(_keyFrames[0].NormalizedKeyTime>=globalProgress)
                {
                    previous = BaseValue;
                    previousNormalizedKT = 0;

                    next = _keyFrames[0].Value;
                    nextNormalizedKT = _keyFrames[0].NormalizedKeyTime;
                    localEasingFunction = _keyFrames[0].EasingFunction;
                }
                else
                {
                    for(int i=1;i<_keyFrames.Count;i++)
                    {
                        var kf = _keyFrames[i];
                        if(kf.NormalizedKeyTime>=globalProgress)
                        {
                            previous = _keyFrames[i - 1].Value;
                            previousNormalizedKT = _keyFrames[i - 1].NormalizedKeyTime;
                            next = kf.Value;
                            nextNormalizedKT = kf.NormalizedKeyTime;
                            localEasingFunction = kf.EasingFunction;
                            break;
                        }
                    }

                }

                _currentValue = Interpolate(
                    previous, next,
                    localEasingFunction(
                        (globalProgress - previousNormalizedKT)/(nextNormalizedKT - previousNormalizedKT)));

                _currentValueDirty = false;

                return _currentValue;
            }
        }

        protected virtual void OnStarted()
        {
            var last = _keyFrames[_keyFrames.Count - 1];
            _duration = last.KeyTime;

            for (int i = 0; i < _keyFrames.Count; i++)
            {
                var kf = _keyFrames[i];
                kf.NormalizedKeyTime = kf.KeyTime/_duration;
            }

            _currentValueDirty = true;
        }

        protected virtual void OnPaused()
        {
            _currentValueDirty = true;
        }

        protected virtual void OnResumed()
        {
            _currentValueDirty = true;
        }

        protected virtual void OnStopped()
        {
            _currentValueDirty = true;
        }

        protected virtual void OnUpdated()
        {
            if (AnimationState == AnimationState.Running)
                _currentValueDirty = true;
        }

        public AnimationState AnimationState { get; private set; }

        public void Start(float currentTime)
        {
            if (AnimationState != AnimationState.Stopped)
                throw new InvalidOperationException("Animation is not stopped");
            _startTime = currentTime;
            _currentTime = currentTime;
            _pausedDuration = 0;
            if (_keyFrames.Count == 0)
                throw new InvalidOperationException("Animation has not key frame");
            AnimationState = AnimationState.Running;
            OnStarted();
        }

        public void Pause(float currentTime)
        {
            if (AnimationState != AnimationState.Running)
                throw new InvalidOperationException("Animation is not running");
            _currentTime = currentTime;
            AnimationState = AnimationState.Paused;
            OnPaused();
        }

        public void Resume(float currentTime)
        {
            if (AnimationState != AnimationState.Paused)
                throw new InvalidOperationException("Animation is not paused");
            _pausedDuration += currentTime - _currentTime;
            _currentTime = currentTime;
            AnimationState = AnimationState.Running;
            OnResumed();
        }

        public void Stop(float currentTime)
        {
            if (AnimationState == AnimationState.Stopped)
                throw new InvalidOperationException("Animation is already stopped");
            AnimationState = AnimationState.Stopped;
            _currentTime = currentTime;
            _startTime = 0;
            _pausedDuration = 0;
            OnStopped();
        }

        public void Update(float currentTime)
        {
            if (AnimationState == AnimationState.Paused)
                _pausedDuration += currentTime - _currentTime;
            _currentTime = currentTime;
            OnUpdated();
        }

        public bool RepeatForever { get; set; }

        public bool AutoReverse { get; set; }


        private float _duration;

        public float AnimationEllapsedTime
        {
            get
            {
                switch (AnimationState)
                {
                    case AnimationState.Stopped:
                        return 0;
                    default:
                        return CurrentTime - StartTime - PausedDuration;
                }
            }
        }


        public float NormalizedAnimationTime
        {
            get
            {
                var normalizedBase = AnimationEllapsedTime / _duration;
                if (normalizedBase <= 1)
                    return normalizedBase;
                var step = (int)Math.Floor(normalizedBase);
                normalizedBase -= step;
                if (AutoReverse)
                {
                    if (!RepeatForever && step > 1)
                        return 1;
                    var mod2 = step % 2;
                    if (mod2 == 1)
                        normalizedBase = 1 - normalizedBase;
                    return normalizedBase;
                }

                if (!RepeatForever)
                    return 1;
                return normalizedBase;
            }
        }

        public EasingFunction GlobalEasingFunction { get; set; }
    }
}
